package com.hanxiaozhang.retry;

import lombok.extern.slf4j.Slf4j;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/**
 * 〈一句话功能简述〉<br>
 * 〈〉
 *
 * @author hanxinghua
 * @create 2022/12/29
 * @since 1.0.0
 */
@Slf4j
@Service
@EnableRetry
public class SpringRetryService {


    /*
    重试策略：
    NeverRetryPolicy：只允许调用RetryCallback一次，不允许重试。
    AlwaysRetryPolicy：允许无限重试，直到成功，此方式逻辑不当会导致死循环。
    SimpleRetryPolicy：固定次数重试策略，默认重试最大次数为3次，RetryTemplate默认使用的策略。
    TimeoutRetryPolicy：超时时间重试策略，默认超时时间为1秒，在指定的超时时间内允许重试。
    ExceptionClassifierRetryPolicy：设置不同异常的重试策略，类似组合重试策略，区别在于这里只区分不同异常的重试。
    CircuitBreakerRetryPolicy：有熔断功能的重试策略，需设置3个参数openTimeout、resetTimeout和delegate。
    CompositeRetryPolicy：组合重试策略，有两种组合方式，乐观组合重试策略是指只要有一个策略允许即可以重试，
                         悲观组合重试策略是指只要有一个策略不允许即可以重试，但不管哪种组合方式，组合中的每一个策略都会执行。

    重试回退策略：
    NoBackOffPolicy：无退避算法策略，每次重试时立即重试。
    FixedBackOffPolicy：固定时间的退避策略，需设置参数sleeper和backOffPeriod，sleeper指定等待策略，
                        默认是Thread.sleep，即线程休眠，backOffPeriod指定休眠时间，默认1秒。
    UniformRandomBackOffPolicy：随机时间退避策略，需设置sleeper、minBackOffPeriod和maxBackOffPeriod，
                        该策略在minBackOffPeriod、maxBackOffPeriod之间取一个随机休眠时间，minBackOffPeriod默认500毫秒，
                        maxBackOffPeriod默认1500毫秒。
    ExponentialBackOffPolicy：指数退避策略，需设置参数sleeper、initialInterval、maxInterval和multiplier，
                        initialInterval指定初始休眠时间，默认100毫秒，maxInterval指定最大休眠时间，默认30秒，
                        multiplier指定乘数，即，下一次休眠时间为当前休眠时间*multiplier。
    ExponentialRandomBackOffPolicy：随机指数退避策略，引入随机乘数可以实现随机乘数回退。
     */

    /**
     * 方式1
     *
     * @return
     */
    public Boolean retry1() {

        RetryTemplate retryTemplate = new RetryTemplate();

        // 设置重试回退操作策略，主要设置重试间隔时间
        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        // 重试间隔时间ms，默认1000ms
        backOffPolicy.setBackOffPeriod(1000L);
        retryTemplate.setBackOffPolicy(backOffPolicy);

        // 设置重试策略，主要设置：重试异常类型、重试次数，默认3次
        Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();
        exceptionMap.put(RemoteAccessException.class, true);
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3, exceptionMap);
        retryTemplate.setRetryPolicy(retryPolicy);

        // 执行
        Boolean execute = retryTemplate.execute(
                // 重试回调
                retryCallback -> {
                    boolean result = RetryTask.task("retry1");
                    log.info("调用的结果:{}", result);
                    return result;
                },
                // 在所有尝试都用尽后进行有状态重试的回调
                recoveryCallback -> {
                    log.info("已达到最大重试次数 或 或抛出了不重试的异常 后，进行一些操作");
                    return false;
                }
        );

        log.info("执行结果:{}", execute);

        return execute;
    }


    /**
     * 方式2
     * <p>
     * 1. RemoteAccessException的异常才重试
     * 2. @Backoff(delay = 2000L,multiplier = 2)) 表示第一次间隔2秒，以后都是次数的2倍，也就是第二次4秒，第三次6秒
     *
     * @return
     */
    @Retryable(value = {RemoteAccessException.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 2))
    public Boolean retry2(String param) {
        return RetryTask.task(param);
    }


    /**
     * recover 机制：达到最大重试次数，或抛出了一个没有指定进行重试的异常，调用此方法
     * <p>
     * Tips：
     * 1. 返回值必须和被重试的函数返回值一致
     * 2. 参数中除了第一个是触发的异常外，后面的参数需要和被重试函数的参数列表一致
     * 3. @Recover注解与@Retryable注解在通过方法中
     *
     * @param e
     * @param param
     * @return
     */
    @Recover
    public Boolean recover(Exception e, String param) {
        log.error("达到最大重试次数,或抛出了一个没有指定进行重试的异常:{},请求参数:{}", e, param);
        return false;
    }


}
